6章 関数
https://gyazo.com/51ba590422cb6160a4960060509a9941
関数(function): 文が集まったもの。サブプログラムと考えることができる。
すべての関数はbody(本体)を持つ。
宣言で定義し、呼び出して実行する
6.1 戻り値
関数の呼び出し自体は式の一種。
式は値を持つ
関数の値はreturnを使って呼び出し側に戻す
returnで戻らない場合や値が指定されてないreturnで戻るときはundefinedになる
6.2 呼び出しと参照
JavaScriptでは関数はオブジェクト
他のオブジェクトと同様に引数として関数に渡したり変数に代入できる
関数の呼び出しと参照を区別する
()をつけると呼び出しになる
()をつけなければ参照になる
実行はされない
code:js
function getGreeting() {
retunr "Hello World!";
}
console.log(getGreeting()); // Hello World!
console.log(getGreeting); // function getGreeting()
const f = getGreeting;
console.log(f()); // Hello World!
const o = {};
o.f = getGreeting;
console.log(o.f()); // Hello World!
console.log(message); // Hello World!
6.3 関数の引数
引数(パラメータ): 関数が呼び出されるまでは存在していない変数
関数宣言における引数を仮引数と呼ぶ。
仮引数は関数の本体でしか値の参照や代入はできない
ただし、関数に引数として渡されたオブジェクトのプロパティを変更すると、関数の外でも変更される
プリミティブとオブジェクトで動作が異なる。
関数内と呼び出し側では別の変数でも、同じオブジェクトを参照している
プリミティブは値型(value type)であると言われる
プリミティブが渡されるときは値がコピーされる
オブジェクトは参照型(reference type)であると言われる
オブジェクトが渡されるときにはどちらの変数も同じオブジェクトを参照する
6.3.1 引数と関数
JavaScriptではどんな関数でも任意個の引数を指定して呼び出すことができる
多くの言語では引数の個数が違えば異なる関数として扱われる
呼び出し時引数を指定しなければ暗黙のうちに値undefinedが指定されたものとして呼び出される
関数定義よりも多くの引数を渡したときにどう処理されるかは後述
code:js
function f(x) {
retunr f内のxの値: ${x};
}
consolelog(f()); // "f内のxの値: undefined"
6.3.2 引数の分割代入
引数に関しても分割代入可能(オブジェクト・配列)
code:js
// object
function getSentence({subject, verb, object}) {
return ${subject} ${verb} ${object};
}
const o = {
subject: "I",
verb: "love",
object: "JavaScript",
};
console.log(getSentence(o)); // "I love JavaScript"
さらにスプレッド演算子...を使って残りの引数をまとめてしまうこともできる
ES5ではargumentsという関数本体でしかアクセスできない特別な変数を使う
この変数は配列に類似したオブジェクトで実際には配列ではない
特別な扱いが必要(もしくは普通の配列に変換する)
code:js
function addPrefix(prefix, ...words) {
const prefixedWords = [];
for(let i=0; i<words.length; i++) {
prefixedWordsi = prefix + wordsi; }
return prefixedWords;
}
console.log(addPrefix("con", "verse", "vex")); // ['converse', 'convex' console.log(addPrefix("非", "プログラマー", "デザイナー", "コーダー"));
6.3.3 デフォルト引数
ES2015から追加。
code:js
function f(a, b = "default", c = 3) {
return ${a} - ${b} - ${c};
}
console.log(f(5,6,7)); // 5 - 6 - 7
console.log(f(5,6)); // 5 - 6 - 3
console.log(f(5)); // 5 - default - 3
console.log(f()); // undefined - default - 3
6.4 オブジェクトのメソッド
メソッド(method): オブジェクトのプロパティとして指定される関数
6.4.1 メソッドのショートハンド(省略記法)
ES2015からメソッドを省略記法で記述することができるようになった。
code:js
const o = {
name: 'Wallace', // プロパティがプリミティブ
bark() { return 'Woof!'; }, // プロパティが関数。簡略形式
}
console.log(o.bark()); // Woof!
6.5 this
this: 関数本体で参照のみ可能な特別な値
通常、オブジェクトとオブジェクトのプロパティである関数(メソッド)を束縛(bind)する
thisは関数の呼び出され方に依存してbindされる点に注意
関数の宣言された場所に依存しない
code:js
const o = {
name: 'Wallace',
speak() { return My name is ${this.name}!; },
}
const speak = o.speak;
console.log(speak === o.speak); // true
console.log(speak()); // "My name is undefined!" strict mode時はエラー
console.log(o.speak()); // "My name is Wallace!"
ネストされた関数からthisにアクセスする場合
code:js
const o = {
name: 'Julie',
greetBackwards: function() {
function getReverseName() {
let nameBackwards = '';
for(let i=this.name.length-1; i>=0; i--) {
nameBackwards += this.namei; }
return nameBackwards;
} // getReverseName の定義終わり
return ${getReverseName()} si eman ym ,olleH;
},
};
console.log(o.greetBackwards());
上のコードはうまく動かない
o.greetBackwardsを呼ぶときはthisはoにbindされる
しかし、getReverseNameをgreetBackwardsの内側から呼ぶときにはthisは別のものにbindされる
この問題が怒らないようにするにはthisを別の変数に代入して覚えておけばよい
selfやthatがよく使われる
アロー関数を使ってもこの問題を避けることができる
code:js
const o = {
name: 'Julie',
greetBackwards: function() {
const self = this; // thisを覚えておく
function getReverseName() {
let nameBackwards = '';
for(let i=self.name.length-1; i>=0; i--) {
nameBackwards += self.namei; }
return nameBackwards;
} // getReverseNameの定義の終わり
return ${getReverseName()} si eman ym ,olleH;
},
};
console.log(o.greetBackwards());
6.6 関数式と無名関数
無名関数(匿名関数): 識別子のない関数
関数式(function expression)を使って呼び出す
関数式は値として関数を返す
変数に代入することもできるしすぐに呼び出すこともできる
code:js
const f = function() {
// ...
}
関数名を指定した上で関数式を値に代入した場合は変数のほうが優先される。
gが優先
関数の外でfを呼ぼうとするとエラー
code:js
const g = function f() {
// ...
}
再帰呼び出し: fの中でfを呼ぶ
code:js
const g = function f(stop) { /* 関数を定義してそれをgに記憶(代入) */
if(stop) {
console.log('f停止');
return;
}
else {
console.log('fは停止していない');
f(true); /* fの中でfを再度呼んでいる */
}
}; /* ここまで関数の定義 */
g(false); /* 上の関数を呼び出し。関数の引数のstopにfalseが代入されて実行される */
console.log("----");
g(true);
/* 実行結果
fは停止していない
f停止
----
f停止
*/
関数宣言と関数式の違いをJavaScriptはどう評価しているか?
コンテキスト
関数宣言が式として使われればそれは関数式となる
そうでない場合は関数宣言となる
両者の区別は実質的にはほとんどない
6.7 アロー関数
ES2015から追加
functionという単語を省略できる
引数がひとつならば()を省略できる
関数本体がひとつの式からなる場合、{}とreturn文を省略できる
アロー関数は無名関数になる
アロー関数を変数に代入することもできるが名前のついた関数を作ることはできない
アロー関数が通常の関数と大きく異なる点
thisが他の変数と同様、語彙的に(lexically)束縛される
アロー関数と通常の関数との小さな違い
オブジェクトのコンストラクタとしては使えない
特別な変数argumentsが使えない
6.8 call, apply, bind
JavaScriptは関数がどこでどのように呼び出されてもthisを何にbindするのか指定できる
call: thisを特定の値に指定した上で関数を呼び出すことができる
すべての関数に対して利用できるメソッド(Function.prototype)
code:js
// 'use strict'; // これを有効にするとgreet()がエラーになる
const bruce = { name: "ブルース" };
const madeline = { name: "マデライン" };
function greet() {
return 私は${this.name}よ!;
}
console.log(greet()); // 私はundefinedよ!
console.log(greet.call(bruce)); // 私はブルースよ!
console.log(greet.call(madeline)); // 私はマデラインよ!
callをオブジェクトの内容を書き換えるのにも使うことができる
code:js
const bruce = { name: "ブルース" };
const madeline = { name: "マデライン" };
function update(birthYear, occupation) { /* 生年と職業を更新する関数 */
this.birthYear = birthYear;
this.occupation = occupation;
}
console.log(bruce); // { name: 'ブルース' }
update.call(bruce, 1949, '歌手'); /* 1949と'歌手'が関数updateの引数になる */
console.log(bruce); // { name: 'ブルース', birthYear: 1949, occupation: '歌手' }
console.log(madeline); // { name: 'マデライン' }
update.call(madeline, 1942, '女優');
console.log(madeline); // { name: 'マデライン', birthYear: 1942, occupation: '女優' }
/* JavaScriptエンジン(処理系)によって出力の形式が少し異なります */
apply: 引数を配列として受け取る
callとよく似ているがcallは引数を直接受け取る
code:js
const bruce = { name: "ブルース" };
const madeline = { name: "マデライン" };
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
console.log(bruce); // { name: 'ブルース' }
update.apply(bruce, 1955, '俳優'); /* 1955と'俳優'が関数updateの引数になる */ console.log(bruce); // { name: 'ブルース', birthYear: 1955, occupation: '俳優' }
console.log(madeline); // { name: 'マデライン' }
console.log(madeline); // { name: 'マデライン', birthYear: 1918, occupation: 'ライター' }
applyはすでに配列が用意されていてそれを引数として使う場合に便利
code:js
'use strict';
console.log(Math.min.apply(null, arr)); // -5
console.log(Math.max.apply(null, arr)); // 15
// Math.minとMath.maxではthisは使われないのでnullを渡している
ES2015のスプレッド演算子を使うとapplyと同じ結果を得ることができる
code:js
'use strict';
const bruce = { name: "ブルース" };
const madeline = { name: "マデライン" };
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
// thisを使うのでcallを使う必要がある
console.log(bruce); // { name: 'ブルース' }
update.call(bruce, ...newBruce); /* apply(bruce, newBruce)と同じ */
console.log(bruce); // { name: 'ブルース', birthYear: 1940, occupation: '武術家' }
// thisを使わないのでスプレッド演算子を使って直接呼び出せる
console.log(Math.min(...arr)); // -5
console.log(Math.max(...arr)); // 15
bind: thisの値をある関数と永続的に結びつけることができる
bindが永続的な束縛を行うことで見つけるのが困難なバグの原因となる可能性があります
有用な場合もあるが注意が必要
code:js
const bruce = { name: "ブルース" };
const madeline = { name: "マデライン" };
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
const updateBruce = update.bind(bruce); /*updateBruceを使うとthisはbruceに束縛される*/
console.log(bruce); // { name: 'ブルース' }
updateBruce(1904, "俳優");
console.log(bruce); // { name: 'ブルース', birthYear: 1904, occupation: '俳優' }
console.log(madeline); // { name: 'マデライン' }
updateBruce.call(madeline, 1274, "王様"); /* madelineに束縛しようとしても... */
console.log(madeline); // { name: 'マデライン' } (madelineは不変)
console.log(bruce); // { name: 'ブルース', birthYear: 1274, occupation: '王様' }
/* ↑ bruceが変わってしまう */
bindに引数を渡すこともできる
code:js
const bruce = { name: "ブルース" };
function update(birthYear, occupation) {
this.birthYear = birthYear;
this.occupation = occupation;
}
const updateBruce1949 = update.bind(bruce, 1949);
console.log(bruce); // { name: 'ブルース' }
updateBruce1949("作詞家");
console.log(bruce); // { name: 'ブルース', birthYear: 1949, occupation: '作詞家' }
6.9 まとめ